﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Resources;
using System.Windows.Shapes;
using MouseKeyboardActivityMonitor;
using MouseKeyboardActivityMonitor.WinApi;
using MouseKeyboardActivityMonitor.Controls;
using SystemWindowsForms = System.Windows.Forms;
using System.Windows.Forms.Integration;
using WindowsInput;
using WindowsInput.Native;
using Microsoft.Expression.Controls;
using System.Collections.ObjectModel;
using System.Windows.Interop;
using Hardcodet.Wpf.TaskbarNotification;

namespace UniPie
{
	/// <summary>
	/// UniPie's main window with the bulk of the business logic.
	/// </summary>
	public partial class MainWindow : Window
	{
		/// <summary>
		/// The minimum time between behavior ativations. Used to prevent reacting when double clicking,
		/// which produced weird results.
		/// </summary>
		public const float MinimumActivationDelay = 0.3f;

		public const string ApplicationName = "UniPie";
		public const double ApplicationVersion = 0.6;
		public const string ApplicationComment = "(Beta)";

		private readonly KeyboardHookListener m_KeyboardHookManager;
		private readonly MouseHookListener m_MouseHookManager;
		private readonly InputSimulator TheInputSimulator;

		/// <summary>
		/// The currently held keys and mouse buttons. Used for checking trigger states.
		/// </summary>
		private ObservableCollection<VirtualKeyCode> HeldVirtualKeycodes = new ObservableCollection<VirtualKeyCode>();

		/// <summary>
		/// Keys and mouse buttons to be suppressed. Used to suppress Behavior invoking key/mouse combos so they don't
		/// mix with injected key/mouse combos in the focused application.
		/// </summary>
		private ObservableCollection<VirtualKeyCode> SuppressedVirtualKeycodes = new ObservableCollection<VirtualKeyCode>();

		/// <summary>
		/// Which behavior is selected in the related listbox.
		/// </summary>
		private int SelectedBehavior = 0;

		/// <summary>
		/// Which behavior is currently active. -1 means none.
		/// </summary>
		private int ActiveBehavior = -1;

		/// <summary>
		/// Which behavior was active the last time. -1 means none of them was activated yet.
		/// </summary>
		private int PreviousActiveBehavior = -1;

		/// <summary>
		/// When was the last time a behavior was activated.
		/// </summary>
		private DateTime BehaviorActivationTime = new DateTime();

		/// <summary>
		/// If we are in recording mode where key/button presses are caught and fed into a
		/// a KeyboardMousePicker control.
		/// </summary>
		private bool IsRecording;

		/// <summary>
		/// The target control of the input recording.
		/// </summary>
		private KeyboardMousePicker RecordingTarget;

		/// <summary>
		/// The borderless window the pie menu control resides in.
		/// </summary>
		/// TODO: It should be made programatically instead.
		private PieWindow ThePieWindow;

		/// <summary>
		/// The pie menu control currently visible.
		/// </summary>
		private PieMenu ThePieMenu;

		/// <summary>
		/// The window handle of the target application.
		/// </summary>
		private int TargetWindowHandle;

		//private int TargetControlHandle;

		/// <summary>
		/// The main window's taskbar icon.
		/// </summary>
		private TaskbarIcon TheTaskbarIcon;

//####################################################################################################################################
#region Utilities

		/// <summary>
		/// DLL import and related struct for querying cursor position.
		/// </summary>
		/// TODO: These should be moved to the Helpers class.
		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetCursorPos(out Helpers.POINTAPI lpPoint);

		[DllImport("user32.dll")]
		static extern int GetForegroundWindow();

		[DllImport("user32.dll")]
		static extern int WindowFromPoint(Helpers.POINTAPI lpPoint);

		[DllImport("user32.DLL")]
		public static extern bool SetForegroundWindow(int hWnd);

		[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
		static extern int SetFocus(int hWnd);

		[DllImport("user32.dll")]
		static extern int GetWindowText(int hWnd, StringBuilder text, int count);

		[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
		static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);

		[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
		static extern int AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);

		[DllImport("kernel32.dll")]
		static extern int GetCurrentThreadId();

		[DllImport("user32.dll")]
		static extern int GetWindowThreadProcessId(int hWnd, int ProcessId);


		/// <summary>
		/// Sends a virtual keycode as the proper target device.
		/// </summary>
		private void SendVirtualKeyCode(VirtualKeyCode vkc, bool IsDown, bool UseDoubleUpHack = false)
		{
			if (IsDown)
			{
				switch (vkc)
				{
					case VirtualKeyCode.LBUTTON:
						TheInputSimulator.Mouse.LeftButtonDown();
						break;
					case VirtualKeyCode.RBUTTON:
						TheInputSimulator.Mouse.RightButtonDown();
						break;
					case VirtualKeyCode.MBUTTON:
						TheInputSimulator.Mouse.MiddleButtonDown();
						break;
					default:
						TheInputSimulator.Keyboard.KeyDown(vkc);
						break;
				}
			}
			else
			{
				switch (vkc)
				{
					case VirtualKeyCode.LBUTTON:
						TheInputSimulator.Mouse.LeftButtonUp();
						if (UseDoubleUpHack) { TheInputSimulator.Mouse.LeftButtonUp(); }
						break;
					case VirtualKeyCode.RBUTTON:
						TheInputSimulator.Mouse.RightButtonUp();
						if (UseDoubleUpHack) { TheInputSimulator.Mouse.RightButtonUp(); }
						break;
					case VirtualKeyCode.MBUTTON:
						TheInputSimulator.Mouse.MiddleButtonUp();
						if (UseDoubleUpHack) { TheInputSimulator.Mouse.RightButtonUp(); }
						break;
					default:
						TheInputSimulator.Keyboard.KeyUp(vkc);
						break;
				}
			}

			System.Threading.Thread.Sleep(50);	// HACK: Certain applications need this delay between events.
		}

		/// <summary>
		/// Gets the coordinates where the pie should be positioned.
		/// (Ie: centers the pie menu on the mouse.)
		/// </summary>
		private System.Windows.Point GetPiePosition(PieMenu pm)
		{
			Helpers.POINTAPI MousePosition = new Helpers.POINTAPI();
			System.Windows.Point ReturnValue = new System.Windows.Point();

			GetCursorPos(out MousePosition);
			ReturnValue.X = MousePosition.X - ((int)System.Math.Round(pm.MaxWidth / 2));
			ReturnValue.Y = MousePosition.Y - ((int)System.Math.Round(pm.MaxHeight / 2));

			return ReturnValue;
		}

		/// <summary>
		/// Lists both the held and suppressed keys, as UniPie understands them.
		/// Used for debugging purposes.
		/// </summary>
		private void PrintVirtualKeyCodeLists()
		{
			string Held = "";
			string Supp = "";

			foreach (VirtualKeyCode vkc in HeldVirtualKeycodes)
			{
				Held = Held + vkc + "\n";
			}
			foreach (VirtualKeyCode vkc in SuppressedVirtualKeycodes)
			{
				Supp = Supp + vkc + "\n";
			}

			this.TextBlock_Held.Text = Held;
			this.TextBlock_Supp.Text = Supp;
		}

		/// <summary>
		/// Custom command to be executed when left click occurs on the taskbar icon.
		/// </summary>
		private class RestoreWindow : ICommand
		{
			public void Execute(object parameter)
			{
				Window w = parameter as Window;

				if (w != null)
				{
					w.WindowState = WindowState.Normal;
					w.Activate();
				}
			}

			public bool CanExecute(object parameter)
			{
				return true;
			}

			public event EventHandler CanExecuteChanged
			{
				add { }
				remove { }
			}
		}

#endregion

//####################################################################################################################################
#region Core functions

		/// <summary>
		/// Remaps the input.
		/// </summary>
		/// <param name="IsDown">
		/// Defines if which push related event to send.
		/// </param>
		private void Remapping(bool IsDown)
		{
			if (Data.Behaviors[ActiveBehavior].Response.RemappedCombo.Count > 0)
			{
				foreach (VirtualKeyCode vkc in Data.Behaviors[ActiveBehavior].Response.RemappedCombo)
				{
					SendVirtualKeyCode(vkc, IsDown, true);
				}
			}
		}

		/// <summary>
		/// Sends a key/mouse combo defined in a slice.
		/// </summary>
		private void SendSliceCombo(int SliceIndex)
		{
			Collection<VirtualKeyCode> vkl;

			// TODO: Should we discard this condition and link slice 0's ComboToSend to TriggerState.KeyboardMouseCombo?
			if (SliceIndex == 0)
			{
				// For Slice #0 (aka center slice) we re-send the already suppressed invoking combo.
				vkl = Data.Behaviors[ActiveBehavior].TriggerState.KeyboardMouseCombo;
			}
			else
			{
				// For non center slices we send their combo.
				vkl = Data.Behaviors[ActiveBehavior].Response.SliceDefinitions[SliceIndex].ComboToSend;
			}
			
			// First we go through the virtual keycodes in forward order.
			// Since the collection is ordered so modifiers are sorted to the
			// beginning, they will be "down" before other keys.
			for (int i = 0; i < vkl.Count; i++)
			{
				SendVirtualKeyCode(vkl[i], true);
			}
			
			// The second time around we iterate through the collection in reverse order
			// so the modifier keys at the beginning get the "up" event last.
			for (int i = vkl.Count-1; i >= 0; i--)
			{
				SendVirtualKeyCode(vkl[i], false);
			}

			// HACK: For some reason the "mouse up" event never arrives for mouse buttons from the loop above
			// if the pie caller and the sent combo is the same. This makes mouse buttons stuck. ("Mouse click"
			// does arrive.) So we send an extra round of ups.
			// TODO: The condition should check for similarities between the pie caller combo and the combo to send.
			if (SliceIndex == 0)
			{
				for (int i = vkl.Count - 1; i >= 0; i--)
				{
					if (vkl[i] == VirtualKeyCode.LBUTTON ||
						vkl[i] == VirtualKeyCode.RBUTTON ||
						vkl[i] == VirtualKeyCode.MBUTTON)
					{
						SendVirtualKeyCode(vkl[i], false);
					}
				}
			}
		}

		/// <summary>
		/// Shows the pie menu.
		/// </summary>
		private void ShowPie()
		{
			System.Windows.Point PieLocation = new System.Windows.Point();

			// We only set up the pie if its different from the previous one or was modified.
			// TODO: Don't share pies but make one for each non-remapping behavior and hide the
			// unwanted ones.
			if (ActiveBehavior != PreviousActiveBehavior || Data.Behaviors[ActiveBehavior].IsDirty)
			{
				ThePieMenu.SliceDefinitions = Data.Behaviors[ActiveBehavior].Response.SliceDefinitions;
				ThePieMenu.UpdateAllSliceVisualStates();
			}

			PieLocation = GetPiePosition(ThePieMenu);
			ThePieWindow.Left = PieLocation.X;
			ThePieWindow.Top = PieLocation.Y;
			ThePieWindow.Show();
		}

		/// <summary>
		/// Hides a given pie.
		/// </summary>
		private bool HidePie()
		{
			ThePieWindow.Hide();
			return true;
		}

		/// <summary>
		/// Activates behavior.
		/// </summary>
		private void ActivateBehavior(int BehaviorIndex)
		{
			// We go through the currently held virtual key codes...
			foreach (VirtualKeyCode vkc in HeldVirtualKeycodes)
			{
				// ...and if there are any new ones...
				if (!SuppressedVirtualKeycodes.Contains(vkc))
				{
					//...add them to the to be suppressed list to stop the related "down" events.
					SuppressedVirtualKeycodes.Add(vkc);
					SuppressedVirtualKeycodes = Helpers.SortVirtualKeyCodes(SuppressedVirtualKeycodes);

					// Fake "up" event to make alerady pressed keys go away completely.
					// BUG: Breaks pie menus!
					//SendVirtualKeyCode(vkc, false);
				}
			}

			Log.i(Data.Behaviors[BehaviorIndex].Name + " is activated.");
			ActiveBehavior = BehaviorIndex;

			if (Data.Behaviors[ActiveBehavior].Response.UseRemapping)
			{
				// We start to hold down the injected combo while the trigger state is present.
				Remapping(true);
			}
			else
			{
				// We make sure we have the handle of the target window.
				if (TargetWindowHandle == -1)
				{
					TargetWindowHandle = GetForegroundWindow();
				}
				ShowPie();
			}

			BehaviorActivationTime = DateTime.Now;
		}

		/// <summary>
		/// Deactivates a behavior.
		/// </summary>
		private void DeactivateBehavior()
		{
			if (ActiveBehavior != -1)
			{
				Log.i(Data.Behaviors[ActiveBehavior].Name + " is deactivated.");

				if (Data.Behaviors[ActiveBehavior].Response.UseRemapping)
				{
					// We let go of the injected combo.
					Remapping(false);
				}
				else
				{
					HidePie();
					
					// We focus the original window.
					SetForegroundWindow(TargetWindowHandle);

					// If a slice was actually selected...
					if (ThePieMenu.SelectedSliceIndex > -1)
					{
						// ...then we send its combo.
						SendSliceCombo(ThePieMenu.SelectedSliceIndex);
					}	
				}

				PreviousActiveBehavior = ActiveBehavior;
				ActiveBehavior = -1;
				TargetWindowHandle = -1;
			}
			else { Log.i("Redundant deacivation!"); }
		}

#endregion

//####################################################################################################################################
#region Checks

		/// <summary>
		/// Checks how much time elapsed between the last reaction call. Used to work around doubclicks messing with event handling.
		/// </summary>
		public bool DelayMatch()
		{
			TimeSpan TimeDifference = new TimeSpan();
			DateTime CurrentTime = new DateTime();

			CurrentTime = DateTime.Now;
			TimeDifference = CurrentTime.Subtract(BehaviorActivationTime);

			if (TimeDifference.TotalSeconds >= MinimumActivationDelay) { return true; }
			else { return false; }
		}

		/// <summary>
		/// Checks foreground windows if they fulfill TriggerState conditions.
		/// </summary>
		public bool ApplicationMatch(ApplicationFilter af)
		{
			const int nChars = 100;
			int handle = 0;
			StringBuilder WindowText = new StringBuilder(nChars);
			StringBuilder WindowClass = new StringBuilder(nChars);
			bool ReturnValue = true;

			// TODO: Remapping (or even all behaviors?) should be disabled
			// while the UniPie main window is in focus.

			// If not IsSystemWide then we start doing detailed checks.
			if (!af.IsSystemWide)
			{
				handle = GetForegroundWindow();
				if (af.WindowCaption.Length > 0)
				{
					GetWindowText(handle, WindowText, WindowText.Capacity);
					// TODO: Make it work with comma separated tokens.
					if (WindowText.ToString().ToLower().Contains(af.WindowCaption.ToLower()))
					{
						TargetWindowHandle = handle;
					}
					else
					{
						ReturnValue = false;
					}
				}

				if (af.ApplicationClass.Length > 0)
				{
					GetClassName(handle, WindowClass, WindowClass.Capacity);
					// TODO: Make it work with comma separated tokens.
					if (WindowClass.ToString().ToLower().Contains(af.ApplicationClass.ToLower()))
					{
						TargetWindowHandle = handle;
					}
					else
					{
						ReturnValue = false;
					}
				}
			}

			return ReturnValue;
		}

		/// <summary>
		/// Checks two sets of virtual key codes and returns how equal they are.
		/// </summary>
		public bool VirtualKeyCodesMatch(ObservableCollection<VirtualKeyCode> KeysA, ObservableCollection<VirtualKeyCode> KeysB)
		{
			bool ReturnValue = false;

			if (KeysA.Count == KeysB.Count)
			{
				ReturnValue = Helpers.SortVirtualKeyCodes(KeysA).SequenceEqual(Helpers.SortVirtualKeyCodes(KeysB));
			}
			return ReturnValue;
		}

		/// <summary>
		/// Checks behaviors and activates one if necessary.
		/// </summary>
		public void CheckBehaviors()
		{
			int i = 0;

			foreach (Behavior ActBehavior in Data.Behaviors)
			{
				if (VirtualKeyCodesMatch(HeldVirtualKeycodes, ActBehavior.TriggerState.KeyboardMouseCombo) &&
					ApplicationMatch(ActBehavior.TriggerState.ApplicationFilter) &&
					ActiveBehavior == -1)	// Only one behavior can be active at any given time.
				{
					ActivateBehavior(i);
					return;
				}
				i++;
			}
		}

#endregion

//####################################################################################################################################
#region Control event handlers

		/// <summary>
		/// Handles the window loaded event.
		/// </summary>
		private void Window_Loaded(object sender, RoutedEventArgs e)
		{
			// Not used since it's called multiple times... o_O	
		}

		
		private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
		{	
		}

		/// <summary>
		/// Handles the Window_StateChanged event.
		/// </summary>
		private void Window_StateChanged(object sender, EventArgs e)
		{
			// When minimized we make the window disappear from the taskbar.
			// TODO: Hide it from ALT+TAB too.
			if (WindowState == WindowState.Minimized)
			{
				Data.SaveData();
				WindowStyle = WindowStyle.None;
				ShowInTaskbar = false;
				TheTaskbarIcon.Visibility = Visibility.Visible;
			}
			// When not minimized the main window should appear in the taskbar.
			else
			{
				WindowStyle = WindowStyle.SingleBorderWindow;
				ShowInTaskbar = true;
				TheTaskbarIcon.Visibility = Visibility.Collapsed;
			}
		}

		/// <summary>
		/// Handles the window closing event.
		/// </summary>
		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			/*MessageBoxResult result;
			string msg = "Do you really want to quit? (Pie menus will not be available.)";

			result = MessageBox.Show( msg,
									"Quit confirmation",
									MessageBoxButton.YesNo,
									MessageBoxImage.Warning);
			if (result == MessageBoxResult.No)
			{
				// If user doesn't want to close, cancel closure
				e.Cancel = true;
			}*/
		}

		/// <summary>
		/// Handles quit.
		/// </summary>
		private void HandleQuit(object sender, RoutedEventArgs e)
		{
			this.Close();
		}

		/// <summary>
		/// Handles the window closed event.
		/// </summary>
		private void Window_Closed(object sender, EventArgs Event)
		{
			Log.i("Shutting down.");
			Data.SaveData();
			m_KeyboardHookManager.Dispose();
			m_MouseHookManager.Dispose();

			ThePieWindow.Close();
		}

		/// <summary>
		/// Handles settings save.
		/// </summary>
		private void HandleSaveSettings(object sender, RoutedEventArgs e)
		{
			Data.SaveData();
		}

		/// <summary>
		/// Handles adding a new behavior.
		/// </summary>
		private void HandleNewBehavior(object sender, RoutedEventArgs e)
		{
			// Insert new behavior after selected one.
			Data.AddBehavior(SelectedBehavior+1);
		}

		/// <summary>
		/// Handles deleting a behavior.
		/// </summary>
		private void HandleDeleteBehavior(object sender, RoutedEventArgs e)
		{
			if (Data.Behaviors.Count > 1)
			{
				Data.DeleteBehavior(SelectedBehavior);

			}
			else Log.i("Can not remove last behavior.");

		}

		/// <summary>
		/// Handles behavior sorting.
		/// </summary>
		private void HandleSortBehaviors(object sender, RoutedEventArgs e)
		{
			Data.SortBehaviors();
		}

		/// <summary>
		/// Handles behavior duplication.
		/// </summary>
		private void HandleDuplicateBehavior(object sender, RoutedEventArgs e)
		{
			Data.DuplicateBehavior(SelectedBehavior);
		}

		/// <summary>
		/// Handles the selection change in the behavior listbox.
		/// </summary>
		private void HandleBehaviorSelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			ListBox lb = (ListBox)sender;

			if (lb != null)
			{
				// If we lost selection for any reason...
				if (lb.SelectedIndex == -1)
				{
					// ...then we force select the first item since
					// normally the list can not be empty.
					lb.SelectedIndex = 0;
					SelectedBehavior = 0;
				}
				else // If selection has just simply changed...
				{
					// ...then we keep track of it.
					SelectedBehavior = lb.SelectedIndex;
				}
			}
		}

		/// <summary>
		/// Handle changes in KeyboardMousePicker controls.
		/// </summary>
		private void HandleKeyboardMousePickerChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			KeyboardMousePicker kmp = (KeyboardMousePicker) sender;
			IsRecording = kmp.IsRecording;

			if (IsRecording)
			{
				RecordingTarget = kmp;
			}
			else
			{
				RecordingTarget = null;
			}
		}

		/// <summary>
		/// Handle changes in PieMenu controls.
		/// </summary>
		private void HandlePieMenuChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			PieMenu pm = (PieMenu)sender;

			if (pm != null)
			{
				pm.UpdateSelectedSliceDefinition();
			}
		}

		/// <summary>
		/// Handle changes in PieMenu setup's SliceEnabled control.
		/// </summary>
		private void HandleSliceEnabledChanged(object sender, RoutedEventArgs e)
		{
			CheckBox cb = (CheckBox)sender;

			if (cb != null)
			{
				// We manually update the pie menu because enabling/disabling a
				// slice involves changing visual states which can not be done
				// through data binding.
				PieMenuPreview.UpdateSelectedSliceDefinition();

				// Let's mark the currently edited behavior as dirty so the
				// related pie menu will be rebuilt.
				MarkSelectedBehaviorAsDirty(sender, e);
			}
		}

		/// <summary>
		/// Marks the selected behavior as dirty.
		/// It's usually called from controls which modify behavior properties.
		/// </summary>
		private void MarkSelectedBehaviorAsDirty(object sender, RoutedEventArgs e)
		{
			Data.Behaviors[SelectedBehavior].IsDirty = true;
		}


		/// <summary>
		/// Marks the selected behavior as dirty.
		/// It's usually called from controls which modify behavior properties.
		/// </summary>
		private void MinimizeWindow(object sender, RoutedEventArgs e)
		{
			this.WindowState = WindowState.Minimized;
		}

#endregion

//####################################################################################################################################
#region Input event handlers

		/// <summary>
		/// Processes a key down event.
		/// </summary>
		public void HandleKeyDown(object sender, SystemWindowsForms.KeyEventArgs Event)
		{
			KeyEventArgsExt EventExt = Event as KeyEventArgsExt;
			VirtualKeyCode vkc = Helpers.GetVirtualKeyCode(Event.KeyCode);

			// If we ARE recording input into a KeyboardMousePicker control...
			if (IsRecording && RecordingTarget != null)
			{
				// If the target control already has this virtual key code...
				if (RecordingTarget.KeyboardMouseCombo.Contains(vkc))
				{
					// ...then we remove it. This basically makes a toggle mechanic.
					RecordingTarget.KeyboardMouseCombo.Remove(vkc);
				}
				else
				{
					// If its not present then we add it.
					RecordingTarget.KeyboardMouseCombo.Add(vkc);
				}

				// We suppress the key to make sure no one will react to it, not UniPie, not other apps.
				EventExt.Handled = true;
			}
			else // If we're NOT recording input.
			{
				// We do not care about any injected events, at all, ever.
				if (EventExt.IsKeyInjected)
				{
					Log.i("Injected key down '" + vkc + "', not caring.");
				}
				else
				{
					// If the currently pressed key is new...
					if (!HeldVirtualKeycodes.Contains(vkc))
					{
						// ...then we add it to the known held virtual key codes list.
						HeldVirtualKeycodes.Add(vkc);

						// We sort the list for consistency.
						HeldVirtualKeycodes = Helpers.SortVirtualKeyCodes(HeldVirtualKeycodes);

						// And since the newly pressed key might be part of a behavior caller combo,
						// we check behaviors and their trigger states.
						CheckBehaviors();
					}

					// If the pressed key is on our to-be-suppressed list...
					if (SuppressedVirtualKeycodes.Contains(vkc))
					{
						// ...then we suppress it.
						EventExt.Handled = true;
					}
				}
			}

			// We update the debugger textblocks so they reflect the current state of the
			// Held and Suppressed virtual key code lists.
			PrintVirtualKeyCodeLists();
		}

		/// <summary>
		/// Processes a key press event.
		/// </summary>
		public void HandleKeyPress(object sender, SystemWindowsForms.KeyPressEventArgs Event)
		{
		}

		/// <summary>
		/// Processes a key up event.
		/// </summary>
		public void HandleKeyUp(object sender, SystemWindowsForms.KeyEventArgs Event)
		{
			KeyEventArgsExt EventExt = Event as KeyEventArgsExt;
			VirtualKeyCode vkc = Helpers.GetVirtualKeyCode(Event.KeyCode);

			// If we ARE recording input into a KeyboardMousePicker control...
			if (IsRecording && RecordingTarget != null)
			{
				// Right now the recording has a toggling mechanic so we don't care about up events.
			}
			else // If we're NOT recording input.
			{
				// We do not care about injected events.
				if (EventExt.IsKeyInjected)
				{
					Log.i("Injected key up '" + vkc + "', not caring.");
				}
				else
				{
					// If it's in the held keys list (probably is)...
					if (HeldVirtualKeycodes.Contains(vkc))
					{
						// ...then we register that the key is not being held anymore.
						HeldVirtualKeycodes.Remove(vkc);
					}

					// If it's on the to-be-suppressed list...
					if (SuppressedVirtualKeycodes.Contains(vkc))
					{
						// ...then we remove from that too.
						SuppressedVirtualKeycodes.Remove(vkc);
					}

					// We don't check TriggerStates on button release only on press
					// to avoid accidental behavior calls.
					// So we deactivate any activated behavior on release.
					if (ActiveBehavior != -1) { DeactivateBehavior(); }
				}
			}

			// We update the debugger textblocks so they reflect the current state of the
			// Held and Suppressed virtual key code lists.
			PrintVirtualKeyCodeLists();
		}

		/// <summary>
		/// Processes a mouse down event.
		/// </summary>
		public void HandleMouseDown(object sender, MouseEventExtArgs Event)
		{
			VirtualKeyCode vkc = Helpers.GetVirtualKeyCode(Event.Button);

			// If we ARE recording input.
			if (IsRecording && RecordingTarget != null)
			{
				if (RecordingTarget.KeyboardMouseCombo.Contains(vkc))
				{
					RecordingTarget.KeyboardMouseCombo.Remove(vkc);
				}
				else
				{
					RecordingTarget.KeyboardMouseCombo.Add(vkc);
				}
				Event.Handled = true;
			}
			else // If we're NOT recording input.
			{
				if (Event.IsMouseKeyInjected)
				{
					Log.i("Injected mouse down '" + vkc + "', not caring.");
				}
				else
				{
					if (!HeldVirtualKeycodes.Contains(vkc))
					{
						HeldVirtualKeycodes.Add(vkc);
						HeldVirtualKeycodes = Helpers.SortVirtualKeyCodes(HeldVirtualKeycodes);
						CheckBehaviors();
					}

					if (SuppressedVirtualKeycodes.Contains(vkc))
					{
						Event.Handled = true;
					}
				}
			}

			PrintVirtualKeyCodeLists();
		}

		/// <summary>
		/// Processes a mouse up event.
		/// </summary>
		public void HandleMouseUp(object sender, SystemWindowsForms.MouseEventArgs Event)
		{
			MouseEventExtArgs EventExt = Event as MouseEventExtArgs;
			VirtualKeyCode vkc = Helpers.GetVirtualKeyCode(Event.Button);

			// If we ARE recording input.
			if (IsRecording && RecordingTarget != null)
			{
			}
			else // If we're NOT recording input.
			{
				if (EventExt.IsMouseKeyInjected)
				{
					Log.i("Injected mouse up '" + vkc + "', not caring.");
				}
				else
				{
					if (HeldVirtualKeycodes.Contains(vkc))
					{
						HeldVirtualKeycodes.Remove(vkc);
					}

					if (ActiveBehavior != -1)
					{
						EventExt.Handled = true;
						DeactivateBehavior();
					}

					if (SuppressedVirtualKeycodes.Contains(vkc))
					{
						SuppressedVirtualKeycodes.Remove(vkc);
					}
				}
			}

			PrintVirtualKeyCodeLists();
		}

		/// <summary>
		/// Processes a mouse click event.
		/// BUG: The click event arrives before the "up" event. Is that normal?
		/// </summary>
		public void HandleMouseClick(object sender, SystemWindowsForms.MouseEventArgs Event)
		{
			MouseEventExtArgs EventExt = Event as MouseEventExtArgs;
			VirtualKeyCode vkc = Helpers.GetVirtualKeyCode(Event.Button);

			// If we ARE recording input.
			if (IsRecording && RecordingTarget != null)
			{
			}
			else // If we're NOT recording input.
			{
				if (EventExt.IsMouseKeyInjected)
				{
					Log.i("Injected mouse click '" + vkc + "', not caring.");
					// HACK: It seems dicey that sending a mouse up ends up as both a mouse up and a mouse click.
					// For now let's kill the injected click.
					EventExt.Handled = true;
				}
				else
				{
					if (HeldVirtualKeycodes.Contains(vkc))
					{
						HeldVirtualKeycodes.Remove(vkc);
					}

					if (ActiveBehavior != -1)
					{
						EventExt.Handled = true;
						DeactivateBehavior();
					}

					if (SuppressedVirtualKeycodes.Contains(vkc))
					{
						SuppressedVirtualKeycodes.Remove(vkc);
					}
				}
			}

			PrintVirtualKeyCodeLists();
		}

		/// <summary>
		/// Processes a mouse double click event.
		/// </summary>
		public void HandleMouseDoubleClick(object sender, SystemWindowsForms.MouseEventArgs Event)
		{
			MouseEventExtArgs EventExt = Event as MouseEventExtArgs;
			VirtualKeyCode vkc = Helpers.GetVirtualKeyCode(Event.Button);

			// If we ARE recording input.
			if (IsRecording && RecordingTarget != null)
			{
			}
			else // If we're NOT recording input.
			{
				if (EventExt.IsMouseKeyInjected)
				{
					Log.i("Injected mouse double click '" + vkc + "', not caring.");
				}
				else
				{
					if (HeldVirtualKeycodes.Contains(vkc))
					{
						HeldVirtualKeycodes.Remove(vkc);
					}

					if (ActiveBehavior != -1)
					{
						EventExt.Handled = true;
						DeactivateBehavior();
					}

					if (SuppressedVirtualKeycodes.Contains(vkc))
					{
						SuppressedVirtualKeycodes.Remove(vkc);
					}
				}
			}

			PrintVirtualKeyCodeLists();
		}

#endregion

		public MainWindow()
		{    
			InitializeComponent();
			Log.i("Starting up.");

			this.Title = ApplicationName + " v" + ApplicationVersion + " " + ApplicationComment;

			// Initialize mouse and keyboard hooks
			m_KeyboardHookManager = new KeyboardHookListener(new GlobalHooker());
			m_KeyboardHookManager.KeyDown += HandleKeyDown;
			m_KeyboardHookManager.KeyPress += HandleKeyPress;
			m_KeyboardHookManager.KeyUp += HandleKeyUp;
			m_KeyboardHookManager.Enabled = true;

			m_MouseHookManager = new MouseHookListener(new GlobalHooker());
			m_MouseHookManager.MouseDownExt += HandleMouseDown;
			m_MouseHookManager.MouseUp += HandleMouseUp;

			// HACK: Since MouseUp never fires after a suppressed MouseDown
			// we rely on the MouseClick event to tell if the button was released.
			m_MouseHookManager.MouseClick += HandleMouseClick;

			// BUG: Double clicking screws things up: the related event doesn't
			// happen with a suppressed MouseDown and it also prevents MouseClick
			// from occuring. Therefore we are left with no way of detecting a mouse
			// button release so mouse button is logically stuck on "down" until another
			// mouse click comes along.
			m_MouseHookManager.MouseDoubleClick += HandleMouseDoubleClick;
			m_MouseHookManager.Enabled = true;

			TheInputSimulator = new InputSimulator();

			System.Windows.Data.CollectionViewSource behaviorViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("behaviorViewSource")));
			behaviorViewSource.Source = Data.Behaviors;

			ThePieWindow = new PieWindow();
			ThePieMenu = new PieMenu();
			ThePieMenu.Name = "ThePieMenu";
			ThePieWindow.Content = ThePieMenu;

			BehaviorActivationTime = DateTime.Now;

			TheTaskbarIcon = new TaskbarIcon();
			TheTaskbarIcon.Visibility = Visibility.Collapsed;
			Stream TheIconStream = Application.GetResourceStream(Helpers.MakePackUri("Assets/UniPieIcon.ico")).Stream;
			TheTaskbarIcon.Icon = new Icon(TheIconStream);
			TheTaskbarIcon.ToolTipText = ApplicationName + " v" + ApplicationVersion;
			TheTaskbarIcon.LeftClickCommand = new RestoreWindow();
			TheTaskbarIcon.LeftClickCommandParameter = this;
			TheTaskbarIcon.DoubleClickCommand = new RestoreWindow();
			TheTaskbarIcon.DoubleClickCommandParameter = this;
        }
	}
}
